Contents

LAST UPDATED

   [1] "Jul 19 2018"

1 Preparation of our R environment

# some technicalities to get started
set.seed(2017) # to make steps depending on random numbers reproducible
# we are going to use functions from the following packages
library('vegan')
library('FastKNN')
library('coin')
library('RColorBrewer')

# to install them, use the following commands from within a Jupyter notebook
#install.packages('vegan',        repos='http://cran.us.r-project.org')
#install.packages('FastKNN',      repos='http://cran.us.r-project.org')
#install.packages('coin',         repos='http://cran.us.r-project.org')
#install.packages('RColorBrewer', repos='http://cran.us.r-project.org')

2 Introduction

Here we’re going to explore some tools for comparing metagenomes. They are generic in the sense that they should work for both amplicon (16S rRNA gene, 18S, ITS amplicons) and shotgun sequencing data. We will focus on taxonomic comparisons, but most techniques are also applicable to functional data (gene families or domains, KEGG, GO annotations etc.), but summarization at pathway level may pose some additional analysis challenges not discussed here.

3 Loading data

We assume to be given a table of read counts which contains one taxon per row and one sample per column (instead of taxa you might have gene families, domains pathways etc.)

# this is data published with H.B. Nielsen et al., Nat. Biotechnol. 2014
#fn.tax.profile <- 'http://www.bork.embl.de/~zeller/public_metagenomics_data/ES-UC-N100_tax-ab-specI.tsv'
#fn.metadata <- 'http://www.bork.embl.de/~zeller/public_metagenomics_data/ES-UC-N100_metadata.tsv'
#fn.tax.profile <- 'http://www.bork.embl.de/~zeller/public_metagenomics_data/ES-CD-N71_tax-ab-specI.tsv'
#fn.metadata <- 'http://www.bork.embl.de/~zeller/public_metagenomics_data/ES-CD-N71_metadata.tsv'

# this is data from G. Zeller et al., Mol. Syst. Biol. 2014
fn.tax.profile <- 'http://www.bork.embl.de/~zeller/public_metagenomics_data/FR-CRC-N141_tax-ab-specI.tsv'
fn.metadata <- 'http://www.bork.embl.de/~zeller/public_metagenomics_data/FR-CRC-N141_metadata.tsv'

# this is data from Feng et al., Gut 2015
#fn.tax.profile <- 'http://www.bork.embl.de/~zeller/public_metagenomics_data/CN-CRC-N128_tax-ab-specI.tsv'
#fn.metadata <- 'http://www.bork.embl.de/~zeller/public_metagenomics_data/CN-CRC-N128_metadata.tsv'

# read abundance matrix
ab <- read.table(fn.tax.profile, quote = '', sep = '\t', header = TRUE,
                 row.names = 1, check.names = FALSE, 
                 stringsAsFactors = FALSE)
ab <- as.matrix(ab)

# read additonal patient metadata
meta <- read.table(fn.metadata, quote = '', sep = '\t', header = TRUE,
                   row.names = 1, check.names = FALSE, 
                   stringsAsFactors = FALSE)

# assert correspondence between abundance and meta data
stopifnot(all(rownames(meta) == colnames(ab)))
cat('Loaded data: n =', ncol(ab), 'samples, p =', nrow(ab), 'taxa (species).\n')
   Loaded data: n = 141 samples, p = 1754 taxa (species).

We will also assume that there are two groups and we are interested in microbiome differences between these groups (as in a standard case-control study)

GROUPS <- unique(meta$Group)
stopifnot(length(GROUPS) == 2)
#print(GROUPS)

# resort according to groups for cleaner visualizations
o <- order(meta$Group)
meta <- meta[o,]
ab <- ab[,o]

4 Data preprocessing

For some comparisons later on, data transformations such as conversion to relative abundances (also called total sum scaling) or rarefying helps to minimize technical bias due to differences in sequencing library size. More information on the effect of these transformations can be found in McMurdie and Holmes, 2014, Weiss et al., 2017 and Costea et al., 2014.

Let’s have a look at the variance in library size.

hist(colSums(ab), 20, col = '#0030A080', main = 'Histogram of library sizes')

One way of dealing with these differences, is to remove the most shallow outliers and use relative or rarefied abundances subsequently.

# remove outlier samples with very few reads
min.lib.size <- 50000
cat(sum(colSums(floor(ab)) < min.lib.size), 'samples have <', min.lib.size, 'reads.\n')
   9 samples have < 50000 reads.
meta <- meta[colSums(floor(ab)) >= min.lib.size,]
ab <- ab[,colSums(floor(ab)) >= min.lib.size]
table(meta$Group)
   
   CRC CTR 
    49  83
# relative abundances
rel.ab <- prop.table(ab, 2)
# rarefied abundances
rar.ab <- t(rrarefy(t(floor(ab)), min.lib.size))
# need matrix transpose to match vegan's conventions about count matrix

# remove taxa whose abundance is zero across all samples
ab <- ab[rowSums(ab) > 0,]
rel.ab <- rel.ab[rowSums(rel.ab) > 0,]
rar.ab <- rar.ab[rowSums(rar.ab) > 0,]
cat('Retained', nrow(ab), 'abundance features,', 
                nrow(rel.ab), 'relative abundance features and', 
                nrow(rar.ab), 'rarefied abundance features.\n')
   Retained 772 abundance features, 772 relative abundance features and 478 rarefied abundance features.

5 Community dissimilarity analysis

In addition to commonly used distances (such as the Euclidean or L1/Manhattan distances), many dissimilarity measures have been proposed by ecologists to compare to habitat samples for differences in observed species content. The R vegan packages offers a large selection.

# calculate pairwise dissimilarities using the vegan package
diss.bray <- vegdist(t(rar.ab), method = 'bray') # transpose to match vegan's conventions
diss.manhattan <- vegdist(t(rel.ab), method = 'manhattan')
diss.euclid <- vegdist(t(rel.ab), method = 'euclidean')
diss.logeuclid <- vegdist(t(log10(rel.ab + 1E-6)), method = 'euclidean')
diss.canberra <- vegdist(t(rar.ab), method = 'canberra')
diss.jaccard <- vegdist(t(rar.ab>0), method = 'jaccard')

diss.list <- list(braycurtis   = as.matrix(diss.bray), 
                  manhattan    = as.matrix(diss.manhattan), 
                  euclidean    = as.matrix(diss.euclid),
                  logeuclidean = as.matrix(diss.logeuclid),
                  canberra     = as.matrix(diss.canberra),
                  jaccard      = as.matrix(diss.jaccard))

d <- 1
image(as.matrix(diss.list[[d]]))

This gives an idea of sample-to-sample distances varying quite a bit, but overall better visualizations exist for this…

5.1 Visualization of a pairwise dissimilarity matrix by ordination

Principal component analysis (PCA) is a commonly used exploratory data analysis tool that is very powerful at revealing structure in a data set. It “rotates” a high-dimensional data set into a coordinate system (orthogonal basis) of linearly uncorrelated principal components (by an orthogonal transformation) in such a way that the first principal component accounts for most of the variance; the second coordinate, orthogonal to the first one, for most of the remaining variance and so on. For the purpose of visual data exploration, one often plots a projection to the first two (or three) principal components - which, intuitively speaking, carry most of the information contained in a high-dimensional data set.

Principal coordinate analysis (PCoA, also called multidemnsional scaling, MDS) can be seen as a more general ordination technique which aims to place samples in a lower dimensional space such that arbitrary, pre-specified distances between samples are preserved as well as possible; these distances can be provided by the user as an input to PCoA.

We first compute the PCA projection…

pcoa.proj <- cmdscale(diss.list[[6]], k = 2)
colnames(pcoa.proj) <- c('PCo 1', 'PCo 2')

… and then visualize it.

plot(pcoa.proj, pch = 16, col = ifelse(meta$Group==GROUPS[1], '#0030A080', '#A0003080'))
legend('bottomleft', GROUPS, pch = 16, col = c('#0030A080', '#A0003080'), bty='n')

5.2 Investigating separation between groups

We can also more directly compare distances within and between groups, e.g. by box plots.

within.idx <- matrix(FALSE, ncol(ab), ncol(ab))
between.idx <- matrix(FALSE, ncol(ab), ncol(ab))
for (i in 1:(ncol(ab)-1)) {
    for (j in (i+1):ncol(ab)) {
        if (meta$Group[i] == meta$Group[j]) {
            within.idx[i,j] <- TRUE
        } else {
            between.idx[i,j] <- TRUE   
        }
    }
}
#image(within.idx)
#image(between.idx)

d <- 5
boxplot(diss.list[[d]][within.idx], diss.list[[d]][between.idx],
        main = paste(names(diss.list)[d], 'distance by group'), 
        names = c('within', 'between'), ylab='Dissimilarity')

5.3 Assessing separation using k-nearest neighbor classification

To quantify whether samples from the same group cluster together, we can assess how often neighboring samples agree with respect to their group membership. This is the concept underlying the simple, yet powerful, k-nearest neighbor classifier implemented below.

# k-nearest neighbor classifier
k <- 5

# we'll use the d-th distance from our list to determine nearest neighbors
d <- 1

nn <- matrix(0, nrow = ncol(ab), k)
rownames(nn) <- colnames(ab)
# agreement between group of neighbors and group of actual sample
nn.agreement <- rep(NA, ncol(ab))
names(nn.agreement) <- colnames(ab)
for (i in 1:ncol(ab)) {
    nn[i,] <- as.numeric(k.nearest.neighbors(i, diss.list[[d]], k = k))
    nn.agreement[i] <- mean(meta$Group[nn[i,]] == meta$Group[i])
}

# Accuracy of kNN classifier
cat('k-NN accuracy ', names(diss.list)[d], ': ', mean(nn.agreement > 0.5), '\n', sep='')
   k-NN accuracy braycurtis: 0.7

5.3.1 Caveat

Although as a researcher you hope to see larger dissimilarities between groups than within, it is not always possible to observe such a clear clustering of microbial abundance data. Even in cases where global dissimilarity does not reveal differences in community composition, not all is lost, as individual taxa may still show significant abundance changes between groups as we will see in the following.

To understand the contribution of individual taxa - varying widely in their mean abundance - to global dissimilarity measures, we are visualizing a crucial difference between the L2 and L1 families of dissimilarity measures by summing up squared or absolute differences across taxa, respectively. L1 based dissimilarities include Manhattan, Bray-Curtis and Canberra, whereas Euclidean and Correlation-based distances are from the L2 family.

s1 <- 1
s2 <- 2
#ab.subset <- log10(rel.ab[,c(s1, s2)] + 1E-6)
ab.subset <- rel.ab[,c(s1, s2)]
# reorder taxa by decreasing mean abundance 
# (and restrict to the top 100 taxa)
ab.subset <- ab.subset[order(rowMeans(ab.subset), decreasing = TRUE)[1:100],]

Comparing L2 distances…

barplot((ab.subset[,1] - ab.subset[,2])^2, 
        names.arg = '', width = 1, space = 0, main='L2 distances')

… to L1 distances…

barplot(abs(ab.subset[,1] - ab.subset[,2]), 
        names.arg = '', width = 1, space = 0, main='L1 distances')

… shows that the L1 distances are influenced by more (less abundant) taxa – recall that the taxa (x-axis) are in decreasing order of their abundance.

6 Testing individual features for association with groups

To test individual taxa (or functional microbiome features) for association with external factors such as disease, I recommend using a nonparametric approach. To assess the differences between two groups, we can use the Wilcoxon test (also called Mann-Whitney U test); for more than two groups the Kruskal-Wallis test. To correct for multiple hypthesis testing, we will employ false discovery rate control Benjamini & Hochberg, 1995, Storey & Tibshirani, 2003.

A comparison of testing approaches can be found in Weiss et al., 2017. LefSe Segata et al., 2011 and SIAMCAT offer neat visualizations of differentially abundant taxa identified by Wilcoxon tests.

# we will only test taxa which reach an abundance of 1E-3 in at least on sample
ab.cutoff <- 1E-3
filt.rel.ab <- rel.ab[apply(rel.ab, 1, max) >= ab.cutoff,]
#filt.rar.ab <- rar.ab[apply(rel.ab[rownames(rar.ab),], 1, max) >= ab.cutoff,]
#filt.raw.ab <- ab[apply(rel.ab[rownames(ab),], 1, max) >= ab.cutoff,]
filt.ab <- filt.rel.ab

# we are going to test each taxon separately
p.values <- rep(1, nrow(filt.ab))
for (t in 1:nrow(filt.ab)) {
  p.values[t] <- wilcox.test(filt.ab[t, meta$Group == GROUPS[1]], 
                             filt.ab[t, meta$Group ==GROUPS[2]])$p.value
}
# afterwards we need to adjust for multiple hypothesis testing using the FDR
p.values <- p.adjust(p.values, method = 'fdr')

sign.idx <- which(p.values < 0.05)
sign.idx <- sign.idx[order(p.values[sign.idx])]
cat('Found', length(sign.idx), 'significantly associated taxa:\n')
   Found 21 significantly associated taxa:
for (i in sign.idx) {
    cat('  ', rownames(filt.ab)[i], ': ', format(p.values[i]), '\n', sep='')
}
     unclassified Fusobacterium [Cluster1482]: 3.6e-07
     unclassified Fusobacterium [Cluster1481]: 3.3e-05
     Porphyromonas asaccharolytica [Cluster1056]: 0.00029
     Pseudoflavonifractor capillosus [Cluster1579]: 0.00069
     unnamed Ruminococcaceae bacterium D16 [Cluster1580]: 0.0034
     Prevotella nigrescens [Cluster1069]: 0.0077
     Peptostreptococcus stomatis [Cluster1530]: 0.0077
     Eubacterium ventriosum [Cluster1629]: 0.0077
     Eubacterium rectale [Cluster1630]: 0.0077
     Eubacterium hallii [Cluster1597]: 0.01
     unnamed Ruminococcus sp. SR1/5 [Cluster1621]: 0.02
     unnamed Parvimonas sp. oral taxon 110 [Cluster1506]: 0.023
     Eubacterium eligens [Cluster1627]: 0.023
     butyrate-producing bacterium [Cluster1595]: 0.024
     unnamed Ruminococcus sp. 5_1_39BFAA [Cluster1620]: 0.029
     Roseburia intestinalis [Cluster1631]: 0.029
     unnamed Parvimonas sp. oral taxon 393 [Cluster1507]: 0.029
     Clostridium symbiosum [Cluster1600]: 0.038
     Clostridium hylemonae [Cluster1607]: 0.038
     Streptococcus salivarius [Cluster1377]: 0.041
     Parvimonas micra [Cluster1505]: 0.047

Using box plots we can try and get an idea how much these abundances differ between groups – I highly recommend looking at this kind of data no matter which methods you’re using to identify differentially abundant taxa.

for (i in sign.idx) {
    sign.taxon.df <- data.frame(filt.rel.ab = log10(filt.rel.ab[i,] + 1E-6), 
                                groups = as.factor(meta$Group))
    boxplot(filt.rel.ab ~ groups, data = sign.taxon.df,
            ylab = 'Relative abundance (log10 scale)', 
            main = rownames(filt.rel.ab)[i], cex.main = 0.8,
            lwd = 2, names = as.character(levels(sign.taxon.df$groups)))
    stripchart(filt.rel.ab ~ groups, data = sign.taxon.df,
               method = "jitter", vertical = TRUE, 
               add = TRUE, pch = 20, cex = 1.5, col = '#0030A080')
}

6.0.1 Caveat

When there are very pronounced changes in community composition, the compositionality of the (relative) microbial abundance data may cause spurious associations, in particular if one or some of the most abundant taxa are altered in abundance.

6.1 Visualization of differential taxa as heatmap

here we use R’s basic image function, but heatmap.2 also offers very nice functionality.

col.scheme <- colorRampPalette(brewer.pal(9,'YlGnBu'))(100)

img.data <- t(log10(filt.rel.ab[sign.idx,] + 1E-6))

# set figure dimensions
par(mar = c(1,0,1,8))

zlim <- c(-6, 0)

image(img.data, xaxt = 'n', yaxt = 'n', xlab = '', ylab = '', bty = 'n',
      zlim = zlim, col = col.scheme)

for (t in 1:length(sign.idx)) {
    mtext(rownames(filt.rel.ab)[sign.idx[t]], side = 4, line = 0.2, cex = 0.5, las = 2,
          at = (t-1) / (length(sign.idx)-1))
}

for (s in 1:ncol(filt.rel.ab)) {
    mtext(meta$Group[s], side = 1, line = 0.2, cex = 0.2, las = 2,
          at = (s-1) / (ncol(filt.rel.ab)-1))
}

# set figure dimensions for color key
par(mar = c(2,2,2,2))

barplot(as.matrix(rep(1,100)), col = col.scheme, horiz = TRUE, border = 0, ylab = '', 
        axes = FALSE)
key.ticks <- seq(zlim[1], zlim[2], length.out=7)
axis(side = 1, at = seq(0, 100, length.out=7), labels = 10^key.ticks, cex.axis = 0.7)
mtext('Rel. ab (log-scale)', side = 3, line = 0.5, at = 50, cex = 0.7, adj = 0.5)

par(mar = c(5,4,4,4))

7 Correlation analysis

To assess the correlation between microbial taxa and an external factor with continous values (in the below we will use host age as an example), Spearman’s correlation coefficient is a good choice as it is robust to the complex distribution of microbiome abundance data; an alternative can be Pearson correlation on suitably transformed abundance data (e.g. log-transformed relative abundances).

# correlate taxa with age
corr.p.values <- rep(1, nrow(filt.rel.ab))

for (t in 1:nrow(filt.rel.ab)) {
  corr.p.values[t] <- cor.test(filt.rel.ab[t,], meta$Age, 
                               method = 'spearman', exact=FALSE)$p.value}
corr.p.values <- p.adjust(corr.p.values, method = 'fdr')
sign.idx <- which(corr.p.values < 0.1)
for (i in sign.idx) {
  cat(rownames(filt.rel.ab)[i], ': ', format(corr.p.values[i]), '\n', sep='')
  plot(meta$Age, log10(filt.rel.ab[i,] + 1E-6), pch = 16, col = 'blue',
       main = rownames(filt.rel.ab)[i], cex.main = 0.8,
       xlab = 'Host Age', ylab = 'Relative abundance (log10-scale)')
  mtext(paste('rho =', format(cor(filt.rel.ab[i,], meta$Age,
                                  method = 'spearman'), digits=3)))
}

7.0.1 Caveat

Although the correlations above may be statistically significant, they are not necessarily robust, in particular when taxa are involved that are not detectable in most of the communities. The compositional nature of microbiome data can moreover cause spurious correlations, see Friedman & Alm, Weiss et al., 2016.

8 Exercises

8.1 Task 1

Explore basic characteristics of your data set using functions head, dim and table (e.g. table(meta$Group); assess distribution of library size (using hist) and find a suitable minimal library size for rarefying.

Optional: Get a feeling for noise in the sampling process corresponding to the sequencing experiment, using a log-log plot of relative abundance of two (arbitrary) samples.

8.2 Task 2

Visualize other dissimilarity matrices using the image function and visually explore whether samples cluster by group using PCoA.

8.3 Task 3

Choose another dissimilarity measure from the vegan package and include it in the list (diss.list).

8.4 Task 4

To better understand how variance differs between individual taxa, explore what statisticians call heteroscedasticity for microbiome data by plotting mean versus variance (use log-scale for both axes). Repeat this task for log-transformed relative abundances to assess whether this tranformation ‘stabilizes’ the variance.

8.4.1 Solution

par(mar = c(5,4,4,4))

plot(log10(apply(rel.ab, 1, mean)), log10(apply(rel.ab, 1, var)),
    pch = 16, col = '#0030A080', main = 'M-V plot of rel. ab.',
    xlab = 'mean (log10-scale)', ylab = 'var (log10-scale)')

plot(apply(log10(rel.ab+1E-6), 1, mean), apply(log10(rel.ab+1E-6), 1, var),
    pch = 16, col = '#0030A080', main = 'M-V plot of logged rel. ab',
    xlab = 'mean', ylab = 'var')

8.5 Task 5

Run kNN classification on the other dissimilarity measures and compare the results. Try to assess whether a particular one works best for the data you’re looking at.

Optional: Modify the kNN classifier to operate on the first two principal coordinates to assess separation in the ordination plot (hint: use Euclidean distance on the projection).

8.6 Task 6

Explore the effect of log transformation on L1 and L2 distances by modifying the barplots to visualize (squared or absolute) differences between log-transformed relative abundances.

8.7 Task 7

Explore how the result of statistical testing changes if abundance filtering is omitted. To understand the difference, it can be instructive to isolate the effect of multiple testing correction (p.adjust).

8.8 Task 8

For a deeper understanding of whether nonparametric statistics should be preferred over parametric ones, it is instructive to visually explore microbial abundance features. Based on these empirical distributions, ask yourself whether the application of parametric methods, such as Student’s T-test assuming Gaussian data, can be justified.

Hint: Prevotella copri (the 32th species in the tables loaded above) is an interesting example; try plotting a histogram of log10(rel.ab[38,] + 1E-6)

Optional: Use the Shapiro Wilks goodness-of-fit test to assess whether microbial abundance data follows a Gaussian distribution (you can also apply it to log-transformed relative abundance, or zero-filtered log-trasnformed relative abundance)

8.9 Task 9

Replace the data sets above by your own data (or any other data that is publicly available) and recapitulate the analyses on that.

Hint: make sure you have metadata in an appropriate format with a column Group that is a two-level factor.

8.10 Task 10

Explore the effects of different abundance preprocessing techniques (relative abundance versus rarefied counts, both with and without log transformation).

8.10.1 Task 10a

Try to combine two data sets to perform cross-study comparisons (meta-analysis). You can e.g. use cbind and rbind on tow of teh data sets above. Introduce an additonal column in the meta data frame that keeps track of the original study.

8.10.2 Task 10b

Use PCoA to explore how strong batch and study (protocol) effects are relative to each other.

8.10.3 Task 10c

Perform univariate Wilcoxon tests on the combined data set. To account for study effects as potential confounders, the coin package offers permutation based tests that allow for blocking (by study). You can substitute these in the code above and compare the results to a naive test on the combined data.

Hint: the syntax is a bit different, the below should help

#pvalue(wilcox_test(meta$Group ~ filt.rel.ab[t,] | as.factor(meta$Study)))

9 Session information

sessionInfo()
   R version 3.5.1 (2018-07-02)
   Platform: x86_64-apple-darwin15.6.0 (64-bit)
   Running under: macOS High Sierra 10.13.2
   
   Matrix products: default
   BLAS: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRblas.0.dylib
   LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib
   
   locale:
   [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
   
   attached base packages:
   [1] stats     graphics  grDevices utils     datasets  methods   base     
   
   other attached packages:
   [1] RColorBrewer_1.1-2 coin_1.2-2         survival_2.42-6    FastKNN_0.0.1     
   [5] vegan_2.5-2        lattice_0.20-35    permute_0.9-4      knitr_1.20        
   [9] BiocStyle_2.8.2   
   
   loaded via a namespace (and not attached):
    [1] Rcpp_0.12.17      cluster_2.0.7-1   magrittr_1.5      splines_3.5.1    
    [5] MASS_7.3-50       multcomp_1.4-8    stringr_1.3.1     tools_3.5.1      
    [9] parallel_3.5.1    grid_3.5.1        nlme_3.1-137      mgcv_1.8-24      
   [13] xfun_0.3          TH.data_1.0-9     modeltools_0.2-22 htmltools_0.3.6  
   [17] yaml_2.1.19       rprojroot_1.3-2   digest_0.6.15     assertthat_0.2.0 
   [21] bookdown_0.7      Matrix_1.2-14     codetools_0.2-15  base64enc_0.1-3  
   [25] pdist_1.2         evaluate_0.11     rmarkdown_1.10    sandwich_2.4-0   
   [29] stringi_1.2.3     compiler_3.5.1    backports_1.1.2   stats4_3.5.1     
   [33] mvtnorm_1.0-8     zoo_1.8-3
LS0tCnRpdGxlOiAiQ29tcGFyYXRpdmUgbWV0YWdlbm9taWNzIHBhcnQgSSAtIGV4cGxvcmF0aW9uLCB2aXN1YWxpemF0aW9uICYgdGVzdGluZyIKb3V0cHV0OgogICBCaW9jU3R5bGU6Omh0bWxfZG9jdW1lbnQ6CiAgICAgIHRvYzogdHJ1ZQogICAgICBkZl9wcmludDogcGFnZWQKICAgICAgc2VsZl9jb250YWluZWQ6IHRydWUKICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgICBoaWdobGlnaHQ6IHRhbmdvCmF1dGhvcjogIkdlb3JnIFplbGxlciwgSmFrb2IgV2lyYmVsIFtFTUJMIEhlaWRlbGJlcmddIgplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KCmBgYHtyIHN0eWxlLCBlY2hvPUZBTFNFLCByZXN1bHRzPSJhc2lzIiwgY2FjaGU9RkFMU0V9CmxpYnJhcnkoImtuaXRyIikKb3B0aW9ucyhkaWdpdHMgPSAyLCB3aWR0aCA9IDgwKQpnb2xkZW5fcmF0aW8gPC0gKDEgKyBzcXJ0KDUpKSAvIDIKb3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHRpZHkgPSBGQUxTRSwgaW5jbHVkZSA9IFRSVUUsCiAgICAgICAgICAgICAgIGRldj1jKCdwbmcnLCAncGRmJywgJ3N2ZycpLCBmaWcuaGVpZ2h0ID0gNSwgCiAgICAgICAgICAgICAgIGZpZy53aWR0aCA9IDQgKiBnb2xkZW5fcmF0aW8sIGNvbW1lbnQgPSAnICAnLCBkcGkgPSAzMDAsCmNhY2hlID0gVFJVRSkKYGBgCgoqKkxBU1QgVVBEQVRFRCoqCgpgYGB7ciwgZWNobz1GQUxTRSwgY2FjaGU9RkFMU0V9CnByaW50KGZvcm1hdChTeXMudGltZSgpLCAiJWIgJWQgJVkiKSkKYGBgCgoKIyBQcmVwYXJhdGlvbiBvZiBvdXIgUiBlbnZpcm9ubWVudAoKYGBge3Igc2V0dXAsIGNhY2hlID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgc29tZSB0ZWNobmljYWxpdGllcyB0byBnZXQgc3RhcnRlZApzZXQuc2VlZCgyMDE3KSAjIHRvIG1ha2Ugc3RlcHMgZGVwZW5kaW5nIG9uIHJhbmRvbSBudW1iZXJzIHJlcHJvZHVjaWJsZQojIHdlIGFyZSBnb2luZyB0byB1c2UgZnVuY3Rpb25zIGZyb20gdGhlIGZvbGxvd2luZyBwYWNrYWdlcwpsaWJyYXJ5KCd2ZWdhbicpCmxpYnJhcnkoJ0Zhc3RLTk4nKQpsaWJyYXJ5KCdjb2luJykKbGlicmFyeSgnUkNvbG9yQnJld2VyJykKCiMgdG8gaW5zdGFsbCB0aGVtLCB1c2UgdGhlIGZvbGxvd2luZyBjb21tYW5kcyBmcm9tIHdpdGhpbiBhIEp1cHl0ZXIgbm90ZWJvb2sKI2luc3RhbGwucGFja2FnZXMoJ3ZlZ2FuJywgICAgICAgIHJlcG9zPSdodHRwOi8vY3Jhbi51cy5yLXByb2plY3Qub3JnJykKI2luc3RhbGwucGFja2FnZXMoJ0Zhc3RLTk4nLCAgICAgIHJlcG9zPSdodHRwOi8vY3Jhbi51cy5yLXByb2plY3Qub3JnJykKI2luc3RhbGwucGFja2FnZXMoJ2NvaW4nLCAgICAgICAgIHJlcG9zPSdodHRwOi8vY3Jhbi51cy5yLXByb2plY3Qub3JnJykKI2luc3RhbGwucGFja2FnZXMoJ1JDb2xvckJyZXdlcicsIHJlcG9zPSdodHRwOi8vY3Jhbi51cy5yLXByb2plY3Qub3JnJykKYGBgCgoKIyBJbnRyb2R1Y3Rpb24KCkhlcmUgd2UncmUgZ29pbmcgdG8gZXhwbG9yZSBzb21lIHRvb2xzIGZvciBjb21wYXJpbmcgbWV0YWdlbm9tZXMuIFRoZXkgYXJlCmdlbmVyaWMgaW4gdGhlIHNlbnNlIHRoYXQgdGhleSBzaG91bGQgd29yayBmb3IgYm90aCBhbXBsaWNvbiAoMTZTIHJSTkEgZ2VuZSwKMThTLCBJVFMgYW1wbGljb25zKSBhbmQgc2hvdGd1biBzZXF1ZW5jaW5nIGRhdGEuIFdlIHdpbGwgZm9jdXMgb24gdGF4b25vbWljCmNvbXBhcmlzb25zLCBidXQgbW9zdCB0ZWNobmlxdWVzIGFyZSBhbHNvIGFwcGxpY2FibGUgdG8gZnVuY3Rpb25hbCBkYXRhIChnZW5lCmZhbWlsaWVzIG9yIGRvbWFpbnMsIEtFR0csIEdPIGFubm90YXRpb25zIGV0Yy4pLCBidXQgc3VtbWFyaXphdGlvbiBhdCBwYXRod2F5CmxldmVsIG1heSBwb3NlIHNvbWUgYWRkaXRpb25hbCBhbmFseXNpcyBjaGFsbGVuZ2VzIG5vdCBkaXNjdXNzZWQgaGVyZS4KCgojIExvYWRpbmcgZGF0YQoKV2UgYXNzdW1lIHRvIGJlIGdpdmVuIGEgdGFibGUgb2YgcmVhZCBjb3VudHMgd2hpY2ggY29udGFpbnMgb25lIHRheG9uIHBlciByb3cKYW5kIG9uZSBzYW1wbGUgcGVyIGNvbHVtbiAoaW5zdGVhZCBvZiB0YXhhIHlvdSBtaWdodCBoYXZlIGdlbmUgZmFtaWxpZXMsCmRvbWFpbnMgcGF0aHdheXMgZXRjLikKCmBgYHtyfQojIHRoaXMgaXMgZGF0YSBwdWJsaXNoZWQgd2l0aCBILkIuIE5pZWxzZW4gZXQgYWwuLCBOYXQuIEJpb3RlY2hub2wuIDIwMTQKI2ZuLnRheC5wcm9maWxlIDwtICdodHRwOi8vd3d3LmJvcmsuZW1ibC5kZS9+emVsbGVyL3B1YmxpY19tZXRhZ2Vub21pY3NfZGF0YS9FUy1VQy1OMTAwX3RheC1hYi1zcGVjSS50c3YnCiNmbi5tZXRhZGF0YSA8LSAnaHR0cDovL3d3dy5ib3JrLmVtYmwuZGUvfnplbGxlci9wdWJsaWNfbWV0YWdlbm9taWNzX2RhdGEvRVMtVUMtTjEwMF9tZXRhZGF0YS50c3YnCiNmbi50YXgucHJvZmlsZSA8LSAnaHR0cDovL3d3dy5ib3JrLmVtYmwuZGUvfnplbGxlci9wdWJsaWNfbWV0YWdlbm9taWNzX2RhdGEvRVMtQ0QtTjcxX3RheC1hYi1zcGVjSS50c3YnCiNmbi5tZXRhZGF0YSA8LSAnaHR0cDovL3d3dy5ib3JrLmVtYmwuZGUvfnplbGxlci9wdWJsaWNfbWV0YWdlbm9taWNzX2RhdGEvRVMtQ0QtTjcxX21ldGFkYXRhLnRzdicKCiMgdGhpcyBpcyBkYXRhIGZyb20gRy4gWmVsbGVyIGV0IGFsLiwgTW9sLiBTeXN0LiBCaW9sLiAyMDE0CmZuLnRheC5wcm9maWxlIDwtICdodHRwOi8vd3d3LmJvcmsuZW1ibC5kZS9+emVsbGVyL3B1YmxpY19tZXRhZ2Vub21pY3NfZGF0YS9GUi1DUkMtTjE0MV90YXgtYWItc3BlY0kudHN2Jwpmbi5tZXRhZGF0YSA8LSAnaHR0cDovL3d3dy5ib3JrLmVtYmwuZGUvfnplbGxlci9wdWJsaWNfbWV0YWdlbm9taWNzX2RhdGEvRlItQ1JDLU4xNDFfbWV0YWRhdGEudHN2JwoKIyB0aGlzIGlzIGRhdGEgZnJvbSBGZW5nIGV0IGFsLiwgR3V0IDIwMTUKI2ZuLnRheC5wcm9maWxlIDwtICdodHRwOi8vd3d3LmJvcmsuZW1ibC5kZS9+emVsbGVyL3B1YmxpY19tZXRhZ2Vub21pY3NfZGF0YS9DTi1DUkMtTjEyOF90YXgtYWItc3BlY0kudHN2JwojZm4ubWV0YWRhdGEgPC0gJ2h0dHA6Ly93d3cuYm9yay5lbWJsLmRlL356ZWxsZXIvcHVibGljX21ldGFnZW5vbWljc19kYXRhL0NOLUNSQy1OMTI4X21ldGFkYXRhLnRzdicKCiMgcmVhZCBhYnVuZGFuY2UgbWF0cml4CmFiIDwtIHJlYWQudGFibGUoZm4udGF4LnByb2ZpbGUsIHF1b3RlID0gJycsIHNlcCA9ICdcdCcsIGhlYWRlciA9IFRSVUUsCiAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwgY2hlY2submFtZXMgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQphYiA8LSBhcy5tYXRyaXgoYWIpCgojIHJlYWQgYWRkaXRvbmFsIHBhdGllbnQgbWV0YWRhdGEKbWV0YSA8LSByZWFkLnRhYmxlKGZuLm1ldGFkYXRhLCBxdW90ZSA9ICcnLCBzZXAgPSAnXHQnLCBoZWFkZXIgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwgY2hlY2submFtZXMgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojIGFzc2VydCBjb3JyZXNwb25kZW5jZSBiZXR3ZWVuIGFidW5kYW5jZSBhbmQgbWV0YSBkYXRhCnN0b3BpZm5vdChhbGwocm93bmFtZXMobWV0YSkgPT0gY29sbmFtZXMoYWIpKSkKY2F0KCdMb2FkZWQgZGF0YTogbiA9JywgbmNvbChhYiksICdzYW1wbGVzLCBwID0nLCBucm93KGFiKSwgJ3RheGEgKHNwZWNpZXMpLlxuJykKYGBgCgpXZSB3aWxsIGFsc28gYXNzdW1lIHRoYXQgdGhlcmUgYXJlIHR3byBncm91cHMgYW5kIHdlIGFyZSBpbnRlcmVzdGVkIGluCm1pY3JvYmlvbWUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGVzZSBncm91cHMgKGFzIGluIGEgc3RhbmRhcmQgY2FzZS1jb250cm9sCnN0dWR5KQoKYGBge3J9CkdST1VQUyA8LSB1bmlxdWUobWV0YSRHcm91cCkKc3RvcGlmbm90KGxlbmd0aChHUk9VUFMpID09IDIpCiNwcmludChHUk9VUFMpCgojIHJlc29ydCBhY2NvcmRpbmcgdG8gZ3JvdXBzIGZvciBjbGVhbmVyIHZpc3VhbGl6YXRpb25zCm8gPC0gb3JkZXIobWV0YSRHcm91cCkKbWV0YSA8LSBtZXRhW28sXQphYiA8LSBhYlssb10KYGBgCgoKIyBEYXRhIHByZXByb2Nlc3NpbmcKCkZvciBzb21lIGNvbXBhcmlzb25zIGxhdGVyIG9uLCBkYXRhIHRyYW5zZm9ybWF0aW9ucyBzdWNoIGFzIGNvbnZlcnNpb24gdG8KcmVsYXRpdmUgYWJ1bmRhbmNlcyAoYWxzbyBjYWxsZWQgdG90YWwgc3VtIHNjYWxpbmcpIG9yIHJhcmVmeWluZyBoZWxwcyB0bwptaW5pbWl6ZSB0ZWNobmljYWwgYmlhcyBkdWUgdG8gZGlmZmVyZW5jZXMgaW4gc2VxdWVuY2luZyBsaWJyYXJ5IHNpemUuCk1vcmUgaW5mb3JtYXRpb24gb24gdGhlIGVmZmVjdCBvZiB0aGVzZSB0cmFuc2Zvcm1hdGlvbnMgY2FuIGJlIGZvdW5kIGluCltNY011cmRpZSBhbmQgSG9sbWVzLCAyMDE0XShodHRwOi8vam91cm5hbHMucGxvcy5vcmcvcGxvc2NvbXBiaW9sL2FydGljbGU/aWQ9MTAuMTM3MS9qb3VybmFsLnBjYmkuMTAwMzUzMSksIApbV2Vpc3MgZXQgYWwuLCAyMDE3XShodHRwczovL21pY3JvYmlvbWVqb3VybmFsLmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvczQwMTY4LTAxNy0wMjM3LXkpCmFuZCBbQ29zdGVhIGV0IGFsLiwgMjAxNF0oaHR0cDovL3d3dy5uYXR1cmUuY29tL25tZXRoL2pvdXJuYWwvdjExL240L2Z1bGwvbm1ldGguMjg5Ny5odG1sKS4KCkxldCdzIGhhdmUgYSBsb29rIGF0IHRoZSB2YXJpYW5jZSBpbiBsaWJyYXJ5IHNpemUuIApgYGB7cn0KaGlzdChjb2xTdW1zKGFiKSwgMjAsIGNvbCA9ICcjMDAzMEEwODAnLCBtYWluID0gJ0hpc3RvZ3JhbSBvZiBsaWJyYXJ5IHNpemVzJykKYGBgCgoKT25lIHdheSBvZiBkZWFsaW5nIHdpdGggdGhlc2UgZGlmZmVyZW5jZXMsIGlzIHRvIHJlbW92ZSB0aGUgbW9zdCBzaGFsbG93Cm91dGxpZXJzIGFuZCB1c2UgcmVsYXRpdmUgb3IgcmFyZWZpZWQgYWJ1bmRhbmNlcyBzdWJzZXF1ZW50bHkuCmBgYHtyfQojIHJlbW92ZSBvdXRsaWVyIHNhbXBsZXMgd2l0aCB2ZXJ5IGZldyByZWFkcwptaW4ubGliLnNpemUgPC0gNTAwMDAKY2F0KHN1bShjb2xTdW1zKGZsb29yKGFiKSkgPCBtaW4ubGliLnNpemUpLCAnc2FtcGxlcyBoYXZlIDwnLCBtaW4ubGliLnNpemUsICdyZWFkcy5cbicpCm1ldGEgPC0gbWV0YVtjb2xTdW1zKGZsb29yKGFiKSkgPj0gbWluLmxpYi5zaXplLF0KYWIgPC0gYWJbLGNvbFN1bXMoZmxvb3IoYWIpKSA+PSBtaW4ubGliLnNpemVdCnRhYmxlKG1ldGEkR3JvdXApCmBgYAoKYGBge3J9CiMgcmVsYXRpdmUgYWJ1bmRhbmNlcwpyZWwuYWIgPC0gcHJvcC50YWJsZShhYiwgMikKIyByYXJlZmllZCBhYnVuZGFuY2VzCnJhci5hYiA8LSB0KHJyYXJlZnkodChmbG9vcihhYikpLCBtaW4ubGliLnNpemUpKQojIG5lZWQgbWF0cml4IHRyYW5zcG9zZSB0byBtYXRjaCB2ZWdhbidzIGNvbnZlbnRpb25zIGFib3V0IGNvdW50IG1hdHJpeAoKIyByZW1vdmUgdGF4YSB3aG9zZSBhYnVuZGFuY2UgaXMgemVybyBhY3Jvc3MgYWxsIHNhbXBsZXMKYWIgPC0gYWJbcm93U3VtcyhhYikgPiAwLF0KcmVsLmFiIDwtIHJlbC5hYltyb3dTdW1zKHJlbC5hYikgPiAwLF0KcmFyLmFiIDwtIHJhci5hYltyb3dTdW1zKHJhci5hYikgPiAwLF0KY2F0KCdSZXRhaW5lZCcsIG5yb3coYWIpLCAnYWJ1bmRhbmNlIGZlYXR1cmVzLCcsIAogICAgICAgICAgICAgICAgbnJvdyhyZWwuYWIpLCAncmVsYXRpdmUgYWJ1bmRhbmNlIGZlYXR1cmVzIGFuZCcsIAogICAgICAgICAgICAgICAgbnJvdyhyYXIuYWIpLCAncmFyZWZpZWQgYWJ1bmRhbmNlIGZlYXR1cmVzLlxuJykKYGBgCgoKIyBDb21tdW5pdHkgZGlzc2ltaWxhcml0eSBhbmFseXNpcwoKSW4gYWRkaXRpb24gdG8gY29tbW9ubHkgdXNlZCBkaXN0YW5jZXMgKHN1Y2ggYXMgdGhlIEV1Y2xpZGVhbiBvciBMMS9NYW5oYXR0YW4KZGlzdGFuY2VzKSwgbWFueSBkaXNzaW1pbGFyaXR5IG1lYXN1cmVzIGhhdmUgYmVlbiBwcm9wb3NlZCBieSBlY29sb2dpc3RzIHRvCmNvbXBhcmUgdG8gaGFiaXRhdCBzYW1wbGVzIGZvciBkaWZmZXJlbmNlcyBpbiBvYnNlcnZlZCBzcGVjaWVzIGNvbnRlbnQuIFRoZSBSCnZlZ2FuIHBhY2thZ2VzIG9mZmVycyBhIGxhcmdlIHNlbGVjdGlvbi4KCmBgYHtyfQojIGNhbGN1bGF0ZSBwYWlyd2lzZSBkaXNzaW1pbGFyaXRpZXMgdXNpbmcgdGhlIHZlZ2FuIHBhY2thZ2UKZGlzcy5icmF5IDwtIHZlZ2Rpc3QodChyYXIuYWIpLCBtZXRob2QgPSAnYnJheScpICMgdHJhbnNwb3NlIHRvIG1hdGNoIHZlZ2FuJ3MgY29udmVudGlvbnMKZGlzcy5tYW5oYXR0YW4gPC0gdmVnZGlzdCh0KHJlbC5hYiksIG1ldGhvZCA9ICdtYW5oYXR0YW4nKQpkaXNzLmV1Y2xpZCA8LSB2ZWdkaXN0KHQocmVsLmFiKSwgbWV0aG9kID0gJ2V1Y2xpZGVhbicpCmRpc3MubG9nZXVjbGlkIDwtIHZlZ2Rpc3QodChsb2cxMChyZWwuYWIgKyAxRS02KSksIG1ldGhvZCA9ICdldWNsaWRlYW4nKQpkaXNzLmNhbmJlcnJhIDwtIHZlZ2Rpc3QodChyYXIuYWIpLCBtZXRob2QgPSAnY2FuYmVycmEnKQpkaXNzLmphY2NhcmQgPC0gdmVnZGlzdCh0KHJhci5hYj4wKSwgbWV0aG9kID0gJ2phY2NhcmQnKQoKZGlzcy5saXN0IDwtIGxpc3QoYnJheWN1cnRpcyAgID0gYXMubWF0cml4KGRpc3MuYnJheSksIAogICAgICAgICAgICAgICAgICBtYW5oYXR0YW4gICAgPSBhcy5tYXRyaXgoZGlzcy5tYW5oYXR0YW4pLCAKICAgICAgICAgICAgICAgICAgZXVjbGlkZWFuICAgID0gYXMubWF0cml4KGRpc3MuZXVjbGlkKSwKICAgICAgICAgICAgICAgICAgbG9nZXVjbGlkZWFuID0gYXMubWF0cml4KGRpc3MubG9nZXVjbGlkKSwKICAgICAgICAgICAgICAgICAgY2FuYmVycmEgICAgID0gYXMubWF0cml4KGRpc3MuY2FuYmVycmEpLAogICAgICAgICAgICAgICAgICBqYWNjYXJkICAgICAgPSBhcy5tYXRyaXgoZGlzcy5qYWNjYXJkKSkKCmQgPC0gMQppbWFnZShhcy5tYXRyaXgoZGlzcy5saXN0W1tkXV0pKQpgYGAKVGhpcyBnaXZlcyBhbiBpZGVhIG9mIHNhbXBsZS10by1zYW1wbGUgZGlzdGFuY2VzIHZhcnlpbmcgcXVpdGUgYSBiaXQsIGJ1dApvdmVyYWxsIGJldHRlciB2aXN1YWxpemF0aW9ucyBleGlzdCBmb3IgdGhpcy4uLgoKIyMgVmlzdWFsaXphdGlvbiBvZiBhIHBhaXJ3aXNlIGRpc3NpbWlsYXJpdHkgbWF0cml4IGJ5IG9yZGluYXRpb24KClByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMgKFBDQSkgaXMgYSBjb21tb25seSB1c2VkIGV4cGxvcmF0b3J5IGRhdGEKYW5hbHlzaXMgdG9vbCB0aGF0IGlzIHZlcnkgcG93ZXJmdWwgYXQgcmV2ZWFsaW5nIHN0cnVjdHVyZSBpbiBhIGRhdGEgc2V0LiBJdAoicm90YXRlcyIgYSBoaWdoLWRpbWVuc2lvbmFsIGRhdGEgc2V0IGludG8gYSBjb29yZGluYXRlIHN5c3RlbSAob3J0aG9nb25hbApiYXNpcykgb2YgbGluZWFybHkgdW5jb3JyZWxhdGVkIHByaW5jaXBhbCBjb21wb25lbnRzIChieSBhbiBvcnRob2dvbmFsCnRyYW5zZm9ybWF0aW9uKSBpbiBzdWNoIGEgd2F5IHRoYXQgdGhlIGZpcnN0IHByaW5jaXBhbCBjb21wb25lbnQgYWNjb3VudHMgZm9yCm1vc3Qgb2YgdGhlIHZhcmlhbmNlOyB0aGUgc2Vjb25kIGNvb3JkaW5hdGUsIG9ydGhvZ29uYWwgdG8gdGhlIGZpcnN0IG9uZSwgZm9yCm1vc3Qgb2YgdGhlIHJlbWFpbmluZyB2YXJpYW5jZSBhbmQgc28gb24uIEZvciB0aGUgcHVycG9zZSBvZiB2aXN1YWwgZGF0YQpleHBsb3JhdGlvbiwgb25lIG9mdGVuIHBsb3RzIGEgcHJvamVjdGlvbiB0byB0aGUgZmlyc3QgdHdvIChvciB0aHJlZSkKcHJpbmNpcGFsIGNvbXBvbmVudHMgLSB3aGljaCwgaW50dWl0aXZlbHkgc3BlYWtpbmcsIGNhcnJ5IG1vc3Qgb2YgdGhlCmluZm9ybWF0aW9uIGNvbnRhaW5lZCBpbiBhIGhpZ2gtZGltZW5zaW9uYWwgZGF0YSBzZXQuCgpQcmluY2lwYWwgY29vcmRpbmF0ZSBhbmFseXNpcyAoUENvQSwgYWxzbyBjYWxsZWQgbXVsdGlkZW1uc2lvbmFsIHNjYWxpbmcsCk1EUykgY2FuIGJlIHNlZW4gYXMgYSBtb3JlIGdlbmVyYWwgb3JkaW5hdGlvbiB0ZWNobmlxdWUgd2hpY2ggYWltcyB0byBwbGFjZQpzYW1wbGVzIGluIGEgbG93ZXIgZGltZW5zaW9uYWwgc3BhY2Ugc3VjaCB0aGF0IGFyYml0cmFyeSwgcHJlLXNwZWNpZmllZApkaXN0YW5jZXMgYmV0d2VlbiBzYW1wbGVzIGFyZSBwcmVzZXJ2ZWQgYXMgd2VsbCBhcyBwb3NzaWJsZTsgdGhlc2UgZGlzdGFuY2VzCmNhbiBiZSBwcm92aWRlZCBieSB0aGUgdXNlciBhcyBhbiBpbnB1dCB0byBQQ29BLgoKV2UgZmlyc3QgY29tcHV0ZSB0aGUgUENBIHByb2plY3Rpb24uLi4KYGBge3J9CnBjb2EucHJvaiA8LSBjbWRzY2FsZShkaXNzLmxpc3RbWzZdXSwgayA9IDIpCmNvbG5hbWVzKHBjb2EucHJvaikgPC0gYygnUENvIDEnLCAnUENvIDInKQpgYGAKLi4uIGFuZCB0aGVuIHZpc3VhbGl6ZSBpdC4KYGBge3J9CnBsb3QocGNvYS5wcm9qLCBwY2ggPSAxNiwgY29sID0gaWZlbHNlKG1ldGEkR3JvdXA9PUdST1VQU1sxXSwgJyMwMDMwQTA4MCcsICcjQTAwMDMwODAnKSkKbGVnZW5kKCdib3R0b21sZWZ0JywgR1JPVVBTLCBwY2ggPSAxNiwgY29sID0gYygnIzAwMzBBMDgwJywgJyNBMDAwMzA4MCcpLCBidHk9J24nKQpgYGAKCgojIyBJbnZlc3RpZ2F0aW5nIHNlcGFyYXRpb24gYmV0d2VlbiBncm91cHMKV2UgY2FuIGFsc28gbW9yZSBkaXJlY3RseSBjb21wYXJlIGRpc3RhbmNlcyB3aXRoaW4gYW5kIGJldHdlZW4gZ3JvdXBzLCBlLmcuCmJ5IGJveCBwbG90cy4KYGBge3J9CndpdGhpbi5pZHggPC0gbWF0cml4KEZBTFNFLCBuY29sKGFiKSwgbmNvbChhYikpCmJldHdlZW4uaWR4IDwtIG1hdHJpeChGQUxTRSwgbmNvbChhYiksIG5jb2woYWIpKQpmb3IgKGkgaW4gMToobmNvbChhYiktMSkpIHsKICAgIGZvciAoaiBpbiAoaSsxKTpuY29sKGFiKSkgewogICAgICAgIGlmIChtZXRhJEdyb3VwW2ldID09IG1ldGEkR3JvdXBbal0pIHsKICAgICAgICAgICAgd2l0aGluLmlkeFtpLGpdIDwtIFRSVUUKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBiZXR3ZWVuLmlkeFtpLGpdIDwtIFRSVUUgICAKICAgICAgICB9CiAgICB9Cn0KI2ltYWdlKHdpdGhpbi5pZHgpCiNpbWFnZShiZXR3ZWVuLmlkeCkKCmQgPC0gNQpib3hwbG90KGRpc3MubGlzdFtbZF1dW3dpdGhpbi5pZHhdLCBkaXNzLmxpc3RbW2RdXVtiZXR3ZWVuLmlkeF0sCiAgICAgICAgbWFpbiA9IHBhc3RlKG5hbWVzKGRpc3MubGlzdClbZF0sICdkaXN0YW5jZSBieSBncm91cCcpLCAKICAgICAgICBuYW1lcyA9IGMoJ3dpdGhpbicsICdiZXR3ZWVuJyksIHlsYWI9J0Rpc3NpbWlsYXJpdHknKQpgYGAKCgojIyBBc3Nlc3Npbmcgc2VwYXJhdGlvbiB1c2luZyBrLW5lYXJlc3QgbmVpZ2hib3IgY2xhc3NpZmljYXRpb24KClRvIHF1YW50aWZ5IHdoZXRoZXIgc2FtcGxlcyBmcm9tIHRoZSBzYW1lIGdyb3VwIGNsdXN0ZXIgdG9nZXRoZXIsIHdlIGNhbgphc3Nlc3MgaG93IG9mdGVuIG5laWdoYm9yaW5nIHNhbXBsZXMgYWdyZWUgd2l0aCByZXNwZWN0IHRvIHRoZWlyIGdyb3VwCm1lbWJlcnNoaXAuIFRoaXMgaXMgdGhlIGNvbmNlcHQgdW5kZXJseWluZyB0aGUgc2ltcGxlLCB5ZXQgcG93ZXJmdWwsCmstbmVhcmVzdCBuZWlnaGJvciBjbGFzc2lmaWVyIGltcGxlbWVudGVkIGJlbG93LgoKYGBge3J9CiMgay1uZWFyZXN0IG5laWdoYm9yIGNsYXNzaWZpZXIKayA8LSA1CgojIHdlJ2xsIHVzZSB0aGUgZC10aCBkaXN0YW5jZSBmcm9tIG91ciBsaXN0IHRvIGRldGVybWluZSBuZWFyZXN0IG5laWdoYm9ycwpkIDwtIDEKCm5uIDwtIG1hdHJpeCgwLCBucm93ID0gbmNvbChhYiksIGspCnJvd25hbWVzKG5uKSA8LSBjb2xuYW1lcyhhYikKIyBhZ3JlZW1lbnQgYmV0d2VlbiBncm91cCBvZiBuZWlnaGJvcnMgYW5kIGdyb3VwIG9mIGFjdHVhbCBzYW1wbGUKbm4uYWdyZWVtZW50IDwtIHJlcChOQSwgbmNvbChhYikpCm5hbWVzKG5uLmFncmVlbWVudCkgPC0gY29sbmFtZXMoYWIpCmZvciAoaSBpbiAxOm5jb2woYWIpKSB7CiAgICBubltpLF0gPC0gYXMubnVtZXJpYyhrLm5lYXJlc3QubmVpZ2hib3JzKGksIGRpc3MubGlzdFtbZF1dLCBrID0gaykpCiAgICBubi5hZ3JlZW1lbnRbaV0gPC0gbWVhbihtZXRhJEdyb3VwW25uW2ksXV0gPT0gbWV0YSRHcm91cFtpXSkKfQoKIyBBY2N1cmFjeSBvZiBrTk4gY2xhc3NpZmllcgpjYXQoJ2stTk4gYWNjdXJhY3kgJywgbmFtZXMoZGlzcy5saXN0KVtkXSwgJzogJywgbWVhbihubi5hZ3JlZW1lbnQgPiAwLjUpLCAnXG4nLCBzZXA9JycpCmBgYAoKCiMjIyBDYXZlYXQKQWx0aG91Z2ggYXMgYSByZXNlYXJjaGVyIHlvdSBob3BlIHRvIHNlZSBsYXJnZXIgZGlzc2ltaWxhcml0aWVzIGJldHdlZW4KZ3JvdXBzIHRoYW4gd2l0aGluLCBpdCBpcyBub3QgYWx3YXlzIHBvc3NpYmxlIHRvIG9ic2VydmUgc3VjaCBhIGNsZWFyCmNsdXN0ZXJpbmcgb2YgbWljcm9iaWFsIGFidW5kYW5jZSBkYXRhLiBFdmVuIGluIGNhc2VzIHdoZXJlIGdsb2JhbApkaXNzaW1pbGFyaXR5IGRvZXMgbm90IHJldmVhbCBkaWZmZXJlbmNlcyBpbiBjb21tdW5pdHkgY29tcG9zaXRpb24sIG5vdCBhbGwKaXMgbG9zdCwgYXMgaW5kaXZpZHVhbCB0YXhhIG1heSBzdGlsbCBzaG93IHNpZ25pZmljYW50IGFidW5kYW5jZSBjaGFuZ2VzCmJldHdlZW4gZ3JvdXBzIGFzIHdlIHdpbGwgc2VlIGluIHRoZSBmb2xsb3dpbmcuCgpUbyB1bmRlcnN0YW5kIHRoZSBjb250cmlidXRpb24gb2YgaW5kaXZpZHVhbCB0YXhhIC0gdmFyeWluZyB3aWRlbHkgaW4gdGhlaXIKbWVhbiBhYnVuZGFuY2UgLSB0byBnbG9iYWwgZGlzc2ltaWxhcml0eSBtZWFzdXJlcywgd2UgYXJlIHZpc3VhbGl6aW5nIGEKY3J1Y2lhbCBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIEwyIGFuZCBMMSBmYW1pbGllcyBvZiBkaXNzaW1pbGFyaXR5IG1lYXN1cmVzCmJ5IHN1bW1pbmcgdXAgc3F1YXJlZCBvciBhYnNvbHV0ZSBkaWZmZXJlbmNlcyBhY3Jvc3MgdGF4YSwgcmVzcGVjdGl2ZWx5LiAKTDEgYmFzZWQgZGlzc2ltaWxhcml0aWVzIGluY2x1ZGUgTWFuaGF0dGFuLCBCcmF5LUN1cnRpcyBhbmQgQ2FuYmVycmEsIHdoZXJlYXMKRXVjbGlkZWFuIGFuZCBDb3JyZWxhdGlvbi1iYXNlZCBkaXN0YW5jZXMgYXJlIGZyb20gdGhlIEwyIGZhbWlseS4KCmBgYHtyfQpzMSA8LSAxCnMyIDwtIDIKI2FiLnN1YnNldCA8LSBsb2cxMChyZWwuYWJbLGMoczEsIHMyKV0gKyAxRS02KQphYi5zdWJzZXQgPC0gcmVsLmFiWyxjKHMxLCBzMildCiMgcmVvcmRlciB0YXhhIGJ5IGRlY3JlYXNpbmcgbWVhbiBhYnVuZGFuY2UgCiMgKGFuZCByZXN0cmljdCB0byB0aGUgdG9wIDEwMCB0YXhhKQphYi5zdWJzZXQgPC0gYWIuc3Vic2V0W29yZGVyKHJvd01lYW5zKGFiLnN1YnNldCksIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjEwMF0sXQpgYGAKCkNvbXBhcmluZyBMMiBkaXN0YW5jZXMuLi4KYGBge3J9CmJhcnBsb3QoKGFiLnN1YnNldFssMV0gLSBhYi5zdWJzZXRbLDJdKV4yLCAKICAgICAgICBuYW1lcy5hcmcgPSAnJywgd2lkdGggPSAxLCBzcGFjZSA9IDAsIG1haW49J0wyIGRpc3RhbmNlcycpCmBgYAoKLi4uIHRvIEwxIGRpc3RhbmNlcy4uLgpgYGB7cn0KYmFycGxvdChhYnMoYWIuc3Vic2V0WywxXSAtIGFiLnN1YnNldFssMl0pLCAKICAgICAgICBuYW1lcy5hcmcgPSAnJywgd2lkdGggPSAxLCBzcGFjZSA9IDAsIG1haW49J0wxIGRpc3RhbmNlcycpCmBgYAouLi4gc2hvd3MgdGhhdCB0aGUgTDEgZGlzdGFuY2VzIGFyZSBpbmZsdWVuY2VkIGJ5IG1vcmUgKGxlc3MgYWJ1bmRhbnQpIHRheGEKLS0gcmVjYWxsIHRoYXQgdGhlIHRheGEgKHgtYXhpcykgYXJlIGluIGRlY3JlYXNpbmcgb3JkZXIgb2YgdGhlaXIgYWJ1bmRhbmNlLgoKCiMgVGVzdGluZyBpbmRpdmlkdWFsIGZlYXR1cmVzIGZvciBhc3NvY2lhdGlvbiB3aXRoIGdyb3VwcwpUbyB0ZXN0IGluZGl2aWR1YWwgdGF4YSAob3IgZnVuY3Rpb25hbCBtaWNyb2Jpb21lIGZlYXR1cmVzKSBmb3IgYXNzb2NpYXRpb24Kd2l0aCBleHRlcm5hbCBmYWN0b3JzIHN1Y2ggYXMgZGlzZWFzZSwgSSByZWNvbW1lbmQgdXNpbmcgYSBub25wYXJhbWV0cmljCmFwcHJvYWNoLiBUbyBhc3Nlc3MgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gdHdvIGdyb3Vwcywgd2UgY2FuIHVzZSB0aGUKV2lsY294b24gdGVzdCAoYWxzbyBjYWxsZWQgTWFubi1XaGl0bmV5IFUgdGVzdCk7IGZvciBtb3JlIHRoYW4gdHdvIGdyb3VwcyB0aGUKS3J1c2thbC1XYWxsaXMgdGVzdC4gVG8gY29ycmVjdCBmb3IgbXVsdGlwbGUgaHlwdGhlc2lzIHRlc3RpbmcsIHdlIHdpbGwKZW1wbG95IGZhbHNlIGRpc2NvdmVyeSByYXRlIGNvbnRyb2wgW0JlbmphbWluaSAmIEhvY2hiZXJnLCAxOTk1XShodHRwczovL3d3dy5qc3Rvci5vcmcvc3RhYmxlLzIzNDYxMDEpLCAKW1N0b3JleSAmIFRpYnNoaXJhbmksIDIwMDNdKGh0dHA6Ly93d3cucG5hcy5vcmcvY29udGVudC8xMDAvMTYvOTQ0MCkuCgpBIGNvbXBhcmlzb24gb2YgdGVzdGluZyBhcHByb2FjaGVzIGNhbiBiZSBmb3VuZCBpbiBbV2Vpc3MgZXQgYWwuLCAyMDE3XShodHRwczovL21pY3JvYmlvbWVqb3VybmFsLmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvczQwMTY4LTAxNy0wMjM3LXkpLgpMZWZTZSBbU2VnYXRhIGV0IGFsLiwgMjAxMV0oaHR0cHM6Ly9nZW5vbWViaW9sb2d5LmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvZ2ItMjAxMS0xMi02LXI2MCkKYW5kIFtTSUFNQ0FUXShodHRwOi8vc2lhbWNhdC5lbWJsLmRlLykgb2ZmZXIgbmVhdCB2aXN1YWxpemF0aW9ucyBvZgpkaWZmZXJlbnRpYWxseSBhYnVuZGFudCB0YXhhIGlkZW50aWZpZWQgYnkgV2lsY294b24gdGVzdHMuCgoKCmBgYHtyfQojIHdlIHdpbGwgb25seSB0ZXN0IHRheGEgd2hpY2ggcmVhY2ggYW4gYWJ1bmRhbmNlIG9mIDFFLTMgaW4gYXQgbGVhc3Qgb24gc2FtcGxlCmFiLmN1dG9mZiA8LSAxRS0zCmZpbHQucmVsLmFiIDwtIHJlbC5hYlthcHBseShyZWwuYWIsIDEsIG1heCkgPj0gYWIuY3V0b2ZmLF0KI2ZpbHQucmFyLmFiIDwtIHJhci5hYlthcHBseShyZWwuYWJbcm93bmFtZXMocmFyLmFiKSxdLCAxLCBtYXgpID49IGFiLmN1dG9mZixdCiNmaWx0LnJhdy5hYiA8LSBhYlthcHBseShyZWwuYWJbcm93bmFtZXMoYWIpLF0sIDEsIG1heCkgPj0gYWIuY3V0b2ZmLF0KZmlsdC5hYiA8LSBmaWx0LnJlbC5hYgoKIyB3ZSBhcmUgZ29pbmcgdG8gdGVzdCBlYWNoIHRheG9uIHNlcGFyYXRlbHkKcC52YWx1ZXMgPC0gcmVwKDEsIG5yb3coZmlsdC5hYikpCmZvciAodCBpbiAxOm5yb3coZmlsdC5hYikpIHsKICBwLnZhbHVlc1t0XSA8LSB3aWxjb3gudGVzdChmaWx0LmFiW3QsIG1ldGEkR3JvdXAgPT0gR1JPVVBTWzFdXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdC5hYlt0LCBtZXRhJEdyb3VwID09R1JPVVBTWzJdXSkkcC52YWx1ZQp9CiMgYWZ0ZXJ3YXJkcyB3ZSBuZWVkIHRvIGFkanVzdCBmb3IgbXVsdGlwbGUgaHlwb3RoZXNpcyB0ZXN0aW5nIHVzaW5nIHRoZSBGRFIKcC52YWx1ZXMgPC0gcC5hZGp1c3QocC52YWx1ZXMsIG1ldGhvZCA9ICdmZHInKQoKc2lnbi5pZHggPC0gd2hpY2gocC52YWx1ZXMgPCAwLjA1KQpzaWduLmlkeCA8LSBzaWduLmlkeFtvcmRlcihwLnZhbHVlc1tzaWduLmlkeF0pXQpjYXQoJ0ZvdW5kJywgbGVuZ3RoKHNpZ24uaWR4KSwgJ3NpZ25pZmljYW50bHkgYXNzb2NpYXRlZCB0YXhhOlxuJykKZm9yIChpIGluIHNpZ24uaWR4KSB7CiAgICBjYXQoJyAgJywgcm93bmFtZXMoZmlsdC5hYilbaV0sICc6ICcsIGZvcm1hdChwLnZhbHVlc1tpXSksICdcbicsIHNlcD0nJykKfQpgYGAKClVzaW5nIGJveCBwbG90cyB3ZSBjYW4gdHJ5IGFuZCBnZXQgYW4gaWRlYSBob3cgbXVjaCB0aGVzZSBhYnVuZGFuY2VzIGRpZmZlciBiZXR3ZWVuIGdyb3VwcyAtLSBJIGhpZ2hseSByZWNvbW1lbmQgbG9va2luZyBhdCB0aGlzIGtpbmQgb2YgZGF0YSBubyBtYXR0ZXIgd2hpY2ggbWV0aG9kcyB5b3UncmUgdXNpbmcgdG8gaWRlbnRpZnkgZGlmZmVyZW50aWFsbHkgYWJ1bmRhbnQgdGF4YS4KYGBge3J9CmZvciAoaSBpbiBzaWduLmlkeCkgewogICAgc2lnbi50YXhvbi5kZiA8LSBkYXRhLmZyYW1lKGZpbHQucmVsLmFiID0gbG9nMTAoZmlsdC5yZWwuYWJbaSxdICsgMUUtNiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwcyA9IGFzLmZhY3RvcihtZXRhJEdyb3VwKSkKICAgIGJveHBsb3QoZmlsdC5yZWwuYWIgfiBncm91cHMsIGRhdGEgPSBzaWduLnRheG9uLmRmLAogICAgICAgICAgICB5bGFiID0gJ1JlbGF0aXZlIGFidW5kYW5jZSAobG9nMTAgc2NhbGUpJywgCiAgICAgICAgICAgIG1haW4gPSByb3duYW1lcyhmaWx0LnJlbC5hYilbaV0sIGNleC5tYWluID0gMC44LAogICAgICAgICAgICBsd2QgPSAyLCBuYW1lcyA9IGFzLmNoYXJhY3RlcihsZXZlbHMoc2lnbi50YXhvbi5kZiRncm91cHMpKSkKICAgIHN0cmlwY2hhcnQoZmlsdC5yZWwuYWIgfiBncm91cHMsIGRhdGEgPSBzaWduLnRheG9uLmRmLAogICAgICAgICAgICAgICBtZXRob2QgPSAiaml0dGVyIiwgdmVydGljYWwgPSBUUlVFLCAKICAgICAgICAgICAgICAgYWRkID0gVFJVRSwgcGNoID0gMjAsIGNleCA9IDEuNSwgY29sID0gJyMwMDMwQTA4MCcpCn0KYGBgCgojIyMgQ2F2ZWF0CldoZW4gdGhlcmUgYXJlIHZlcnkgcHJvbm91bmNlZCBjaGFuZ2VzIGluIGNvbW11bml0eSBjb21wb3NpdGlvbiwgdGhlCmNvbXBvc2l0aW9uYWxpdHkgb2YgdGhlIChyZWxhdGl2ZSkgbWljcm9iaWFsIGFidW5kYW5jZSBkYXRhIG1heSBjYXVzZQpzcHVyaW91cyBhc3NvY2lhdGlvbnMsIGluIHBhcnRpY3VsYXIgaWYgb25lIG9yIHNvbWUgb2YgdGhlIG1vc3QgYWJ1bmRhbnQgdGF4YQphcmUgYWx0ZXJlZCBpbiBhYnVuZGFuY2UuCgojIyBWaXN1YWxpemF0aW9uIG9mIGRpZmZlcmVudGlhbCB0YXhhIGFzIGhlYXRtYXAKaGVyZSB3ZSB1c2UgUidzIGJhc2ljIGltYWdlIGZ1bmN0aW9uLCBidXQgaGVhdG1hcC4yIGFsc28gb2ZmZXJzIHZlcnkgbmljZQpmdW5jdGlvbmFsaXR5LgoKCmBgYHtyfQpjb2wuc2NoZW1lIDwtIGNvbG9yUmFtcFBhbGV0dGUoYnJld2VyLnBhbCg5LCdZbEduQnUnKSkoMTAwKQoKaW1nLmRhdGEgPC0gdChsb2cxMChmaWx0LnJlbC5hYltzaWduLmlkeCxdICsgMUUtNikpCgojIHNldCBmaWd1cmUgZGltZW5zaW9ucwpwYXIobWFyID0gYygxLDAsMSw4KSkKCnpsaW0gPC0gYygtNiwgMCkKCmltYWdlKGltZy5kYXRhLCB4YXh0ID0gJ24nLCB5YXh0ID0gJ24nLCB4bGFiID0gJycsIHlsYWIgPSAnJywgYnR5ID0gJ24nLAogICAgICB6bGltID0gemxpbSwgY29sID0gY29sLnNjaGVtZSkKCmZvciAodCBpbiAxOmxlbmd0aChzaWduLmlkeCkpIHsKICAgIG10ZXh0KHJvd25hbWVzKGZpbHQucmVsLmFiKVtzaWduLmlkeFt0XV0sIHNpZGUgPSA0LCBsaW5lID0gMC4yLCBjZXggPSAwLjUsIGxhcyA9IDIsCiAgICAgICAgICBhdCA9ICh0LTEpIC8gKGxlbmd0aChzaWduLmlkeCktMSkpCn0KCmZvciAocyBpbiAxOm5jb2woZmlsdC5yZWwuYWIpKSB7CiAgICBtdGV4dChtZXRhJEdyb3VwW3NdLCBzaWRlID0gMSwgbGluZSA9IDAuMiwgY2V4ID0gMC4yLCBsYXMgPSAyLAogICAgICAgICAgYXQgPSAocy0xKSAvIChuY29sKGZpbHQucmVsLmFiKS0xKSkKfQoKIyBzZXQgZmlndXJlIGRpbWVuc2lvbnMgZm9yIGNvbG9yIGtleQpwYXIobWFyID0gYygyLDIsMiwyKSkKCmJhcnBsb3QoYXMubWF0cml4KHJlcCgxLDEwMCkpLCBjb2wgPSBjb2wuc2NoZW1lLCBob3JpeiA9IFRSVUUsIGJvcmRlciA9IDAsIHlsYWIgPSAnJywgCiAgICAgICAgYXhlcyA9IEZBTFNFKQprZXkudGlja3MgPC0gc2VxKHpsaW1bMV0sIHpsaW1bMl0sIGxlbmd0aC5vdXQ9NykKYXhpcyhzaWRlID0gMSwgYXQgPSBzZXEoMCwgMTAwLCBsZW5ndGgub3V0PTcpLCBsYWJlbHMgPSAxMF5rZXkudGlja3MsIGNleC5heGlzID0gMC43KQptdGV4dCgnUmVsLiBhYiAobG9nLXNjYWxlKScsIHNpZGUgPSAzLCBsaW5lID0gMC41LCBhdCA9IDUwLCBjZXggPSAwLjcsIGFkaiA9IDAuNSkKCnBhcihtYXIgPSBjKDUsNCw0LDQpKQpgYGAKCgojIENvcnJlbGF0aW9uIGFuYWx5c2lzClRvIGFzc2VzcyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBtaWNyb2JpYWwgdGF4YSBhbmQgYW4gZXh0ZXJuYWwgZmFjdG9yIHdpdGggY29udGlub3VzIHZhbHVlcyAoaW4gdGhlIGJlbG93IHdlIHdpbGwgdXNlIGhvc3QgYWdlIGFzIGFuIGV4YW1wbGUpLCBTcGVhcm1hbidzIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IGlzIGEgZ29vZCBjaG9pY2UgYXMgaXQgaXMgcm9idXN0IHRvIHRoZSBjb21wbGV4IGRpc3RyaWJ1dGlvbiBvZiBtaWNyb2Jpb21lIGFidW5kYW5jZSBkYXRhOyBhbiBhbHRlcm5hdGl2ZSBjYW4gYmUgUGVhcnNvbiBjb3JyZWxhdGlvbiBvbiBzdWl0YWJseSB0cmFuc2Zvcm1lZCBhYnVuZGFuY2UgZGF0YSAoZS5nLiBsb2ctdHJhbnNmb3JtZWQgcmVsYXRpdmUgYWJ1bmRhbmNlcykuCgoKYGBge3J9CiMgY29ycmVsYXRlIHRheGEgd2l0aCBhZ2UKY29yci5wLnZhbHVlcyA8LSByZXAoMSwgbnJvdyhmaWx0LnJlbC5hYikpCgpmb3IgKHQgaW4gMTpucm93KGZpbHQucmVsLmFiKSkgewogIGNvcnIucC52YWx1ZXNbdF0gPC0gY29yLnRlc3QoZmlsdC5yZWwuYWJbdCxdLCBtZXRhJEFnZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAnc3BlYXJtYW4nLCBleGFjdD1GQUxTRSkkcC52YWx1ZX0KY29yci5wLnZhbHVlcyA8LSBwLmFkanVzdChjb3JyLnAudmFsdWVzLCBtZXRob2QgPSAnZmRyJykKc2lnbi5pZHggPC0gd2hpY2goY29yci5wLnZhbHVlcyA8IDAuMSkKZm9yIChpIGluIHNpZ24uaWR4KSB7CiAgY2F0KHJvd25hbWVzKGZpbHQucmVsLmFiKVtpXSwgJzogJywgZm9ybWF0KGNvcnIucC52YWx1ZXNbaV0pLCAnXG4nLCBzZXA9JycpCiAgcGxvdChtZXRhJEFnZSwgbG9nMTAoZmlsdC5yZWwuYWJbaSxdICsgMUUtNiksIHBjaCA9IDE2LCBjb2wgPSAnYmx1ZScsCiAgICAgICBtYWluID0gcm93bmFtZXMoZmlsdC5yZWwuYWIpW2ldLCBjZXgubWFpbiA9IDAuOCwKICAgICAgIHhsYWIgPSAnSG9zdCBBZ2UnLCB5bGFiID0gJ1JlbGF0aXZlIGFidW5kYW5jZSAobG9nMTAtc2NhbGUpJykKICBtdGV4dChwYXN0ZSgncmhvID0nLCBmb3JtYXQoY29yKGZpbHQucmVsLmFiW2ksXSwgbWV0YSRBZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAnc3BlYXJtYW4nKSwgZGlnaXRzPTMpKSkKfQpgYGAKCiMjIyBDYXZlYXQKQWx0aG91Z2ggdGhlIGNvcnJlbGF0aW9ucyBhYm92ZSBtYXkgYmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCwgdGhleSBhcmUKbm90IG5lY2Vzc2FyaWx5IHJvYnVzdCwgaW4gcGFydGljdWxhciB3aGVuIHRheGEgYXJlIGludm9sdmVkIHRoYXQgYXJlIG5vdApkZXRlY3RhYmxlIGluIG1vc3Qgb2YgdGhlIGNvbW11bml0aWVzLiBUaGUgY29tcG9zaXRpb25hbCBuYXR1cmUgb2YgbWljcm9iaW9tZQpkYXRhIGNhbiBtb3Jlb3ZlciBjYXVzZSBzcHVyaW91cyBjb3JyZWxhdGlvbnMsIHNlZSAKW0ZyaWVkbWFuICYgQWxtXShodHRwOi8vam91cm5hbHMucGxvcy5vcmcvcGxvc2NvbXBiaW9sL2FydGljbGU/aWQ9MTAuMTM3MS9qb3VybmFsLnBjYmkuMTAwMjY4NyksIApbV2Vpc3MgZXQgYWwuLCAyMDE2XShodHRwOi8vd3d3Lm5hdHVyZS5jb20vaXNtZWovam91cm5hbC92MTAvbjcvZnVsbC9pc21lajIwMTUyMzVhLmh0bWwpLgoKCiMgRXhlcmNpc2VzCiMjIFRhc2sgMQpFeHBsb3JlIGJhc2ljIGNoYXJhY3RlcmlzdGljcyBvZiB5b3VyIGRhdGEgc2V0IHVzaW5nIGZ1bmN0aW9ucyBoZWFkLCBkaW0gYW5kCnRhYmxlIChlLmcuIHRhYmxlKG1ldGEkR3JvdXApOyBhc3Nlc3MgZGlzdHJpYnV0aW9uIG9mIGxpYnJhcnkgc2l6ZSAodXNpbmcKaGlzdCkgYW5kIGZpbmQgYSBzdWl0YWJsZSBtaW5pbWFsIGxpYnJhcnkgc2l6ZSBmb3IgcmFyZWZ5aW5nLgoKKipPcHRpb25hbDoqKiBHZXQgYSBmZWVsaW5nIGZvciBub2lzZSBpbiB0aGUgc2FtcGxpbmcgcHJvY2VzcyBjb3JyZXNwb25kaW5nCnRvIHRoZSBzZXF1ZW5jaW5nIGV4cGVyaW1lbnQsIHVzaW5nIGEgbG9nLWxvZyBwbG90IG9mIHJlbGF0aXZlIGFidW5kYW5jZSBvZgp0d28gKGFyYml0cmFyeSkgc2FtcGxlcy4KCiMjIFRhc2sgMgpWaXN1YWxpemUgb3RoZXIgZGlzc2ltaWxhcml0eSBtYXRyaWNlcyB1c2luZyB0aGUgaW1hZ2UgZnVuY3Rpb24gYW5kIHZpc3VhbGx5CmV4cGxvcmUgd2hldGhlciBzYW1wbGVzIGNsdXN0ZXIgYnkgZ3JvdXAgdXNpbmcgUENvQS4KCiMjIFRhc2sgMwpDaG9vc2UgYW5vdGhlciBkaXNzaW1pbGFyaXR5IG1lYXN1cmUgZnJvbSB0aGUgdmVnYW4gcGFja2FnZSBhbmQgaW5jbHVkZSBpdCBpbgp0aGUgbGlzdCAoZGlzcy5saXN0KS4KCiMjIFRhc2sgNApUbyBiZXR0ZXIgdW5kZXJzdGFuZCBob3cgdmFyaWFuY2UgZGlmZmVycyBiZXR3ZWVuIGluZGl2aWR1YWwgdGF4YSwgZXhwbG9yZQp3aGF0IHN0YXRpc3RpY2lhbnMgY2FsbCBoZXRlcm9zY2VkYXN0aWNpdHkgZm9yIG1pY3JvYmlvbWUgZGF0YSBieSBwbG90dGluZwptZWFuIHZlcnN1cyB2YXJpYW5jZSAodXNlIGxvZy1zY2FsZSBmb3IgYm90aCBheGVzKS4gUmVwZWF0IHRoaXMgdGFzayBmb3IKbG9nLXRyYW5zZm9ybWVkIHJlbGF0aXZlIGFidW5kYW5jZXMgdG8gYXNzZXNzIHdoZXRoZXIgdGhpcyB0cmFuZm9ybWF0aW9uCidzdGFiaWxpemVzJyB0aGUgdmFyaWFuY2UuCgojIyMgU29sdXRpb24KYGBge3J9CnBhcihtYXIgPSBjKDUsNCw0LDQpKQoKcGxvdChsb2cxMChhcHBseShyZWwuYWIsIDEsIG1lYW4pKSwgbG9nMTAoYXBwbHkocmVsLmFiLCAxLCB2YXIpKSwKICAgIHBjaCA9IDE2LCBjb2wgPSAnIzAwMzBBMDgwJywgbWFpbiA9ICdNLVYgcGxvdCBvZiByZWwuIGFiLicsCiAgICB4bGFiID0gJ21lYW4gKGxvZzEwLXNjYWxlKScsIHlsYWIgPSAndmFyIChsb2cxMC1zY2FsZSknKQoKcGxvdChhcHBseShsb2cxMChyZWwuYWIrMUUtNiksIDEsIG1lYW4pLCBhcHBseShsb2cxMChyZWwuYWIrMUUtNiksIDEsIHZhciksCiAgICBwY2ggPSAxNiwgY29sID0gJyMwMDMwQTA4MCcsIG1haW4gPSAnTS1WIHBsb3Qgb2YgbG9nZ2VkIHJlbC4gYWInLAogICAgeGxhYiA9ICdtZWFuJywgeWxhYiA9ICd2YXInKQpgYGAKCiMjIFRhc2sgNQpSdW4ga05OIGNsYXNzaWZpY2F0aW9uIG9uIHRoZSBvdGhlciBkaXNzaW1pbGFyaXR5IG1lYXN1cmVzIGFuZCBjb21wYXJlIHRoZQpyZXN1bHRzLiBUcnkgdG8gYXNzZXNzIHdoZXRoZXIgYSBwYXJ0aWN1bGFyIG9uZSB3b3JrcyBiZXN0IGZvciB0aGUgZGF0YQp5b3UncmUgbG9va2luZyBhdC4KCioqT3B0aW9uYWw6KiogTW9kaWZ5IHRoZSBrTk4gY2xhc3NpZmllciB0byBvcGVyYXRlIG9uIHRoZSBmaXJzdCB0d28gcHJpbmNpcGFsCmNvb3JkaW5hdGVzIHRvIGFzc2VzcyBzZXBhcmF0aW9uIGluIHRoZSBvcmRpbmF0aW9uIHBsb3QgKGhpbnQ6IHVzZSBFdWNsaWRlYW4KZGlzdGFuY2Ugb24gdGhlIHByb2plY3Rpb24pLgoKIyMgVGFzayA2CkV4cGxvcmUgdGhlIGVmZmVjdCBvZiBsb2cgdHJhbnNmb3JtYXRpb24gb24gTDEgYW5kIEwyIGRpc3RhbmNlcyBieSBtb2RpZnlpbmcKdGhlIGJhcnBsb3RzIHRvIHZpc3VhbGl6ZSAoc3F1YXJlZCBvciBhYnNvbHV0ZSkgZGlmZmVyZW5jZXMgYmV0d2Vlbgpsb2ctdHJhbnNmb3JtZWQgcmVsYXRpdmUgYWJ1bmRhbmNlcy4KCiMjIFRhc2sgNwpFeHBsb3JlIGhvdyB0aGUgcmVzdWx0IG9mIHN0YXRpc3RpY2FsIHRlc3RpbmcgY2hhbmdlcyBpZiBhYnVuZGFuY2UgZmlsdGVyaW5nCmlzIG9taXR0ZWQuIFRvIHVuZGVyc3RhbmQgdGhlIGRpZmZlcmVuY2UsIGl0IGNhbiBiZSBpbnN0cnVjdGl2ZSB0byBpc29sYXRlCnRoZSBlZmZlY3Qgb2YgbXVsdGlwbGUgdGVzdGluZyBjb3JyZWN0aW9uIChwLmFkanVzdCkuCgojIyBUYXNrIDgKRm9yIGEgZGVlcGVyIHVuZGVyc3RhbmRpbmcgb2Ygd2hldGhlciBub25wYXJhbWV0cmljIHN0YXRpc3RpY3Mgc2hvdWxkIGJlCnByZWZlcnJlZCBvdmVyIHBhcmFtZXRyaWMgb25lcywgaXQgaXMgaW5zdHJ1Y3RpdmUgdG8gdmlzdWFsbHkgZXhwbG9yZQptaWNyb2JpYWwgYWJ1bmRhbmNlIGZlYXR1cmVzLiBCYXNlZCBvbiB0aGVzZSBlbXBpcmljYWwgZGlzdHJpYnV0aW9ucywgYXNrCnlvdXJzZWxmIHdoZXRoZXIgdGhlIGFwcGxpY2F0aW9uIG9mIHBhcmFtZXRyaWMgbWV0aG9kcywgc3VjaCBhcyBTdHVkZW50J3MKVC10ZXN0IGFzc3VtaW5nIEdhdXNzaWFuIGRhdGEsIGNhbiBiZSBqdXN0aWZpZWQuCgoqKkhpbnQ6KiogUHJldm90ZWxsYSBjb3ByaSAodGhlIDMydGggc3BlY2llcyBpbiB0aGUgdGFibGVzIGxvYWRlZCBhYm92ZSkgaXMKYW4gaW50ZXJlc3RpbmcgZXhhbXBsZTsgdHJ5IHBsb3R0aW5nIGEgaGlzdG9ncmFtIG9mIApgbG9nMTAocmVsLmFiWzM4LF0gKyAxRS02KWAKCioqT3B0aW9uYWw6KiogVXNlIHRoZSBTaGFwaXJvIFdpbGtzIGdvb2RuZXNzLW9mLWZpdCB0ZXN0IHRvIGFzc2VzcyB3aGV0aGVyCm1pY3JvYmlhbCBhYnVuZGFuY2UgZGF0YSBmb2xsb3dzIGEgR2F1c3NpYW4gZGlzdHJpYnV0aW9uICh5b3UgY2FuIGFsc28gYXBwbHkKaXQgdG8gbG9nLXRyYW5zZm9ybWVkIHJlbGF0aXZlIGFidW5kYW5jZSwgb3IgemVyby1maWx0ZXJlZCBsb2ctdHJhc25mb3JtZWQKcmVsYXRpdmUgYWJ1bmRhbmNlKQoKIyMgVGFzayA5ClJlcGxhY2UgdGhlIGRhdGEgc2V0cyBhYm92ZSBieSB5b3VyIG93biBkYXRhIChvciBhbnkgb3RoZXIgZGF0YSB0aGF0IGlzCnB1YmxpY2x5IGF2YWlsYWJsZSkgYW5kIHJlY2FwaXR1bGF0ZSB0aGUgYW5hbHlzZXMgb24gdGhhdC4KCioqSGludDoqKiBtYWtlIHN1cmUgeW91IGhhdmUgbWV0YWRhdGEgaW4gYW4gYXBwcm9wcmlhdGUgZm9ybWF0IHdpdGggYSBjb2x1bW4KR3JvdXAgdGhhdCBpcyBhIHR3by1sZXZlbCBmYWN0b3IuCgojIyBUYXNrIDEwCkV4cGxvcmUgdGhlIGVmZmVjdHMgb2YgZGlmZmVyZW50IGFidW5kYW5jZSBwcmVwcm9jZXNzaW5nIHRlY2huaXF1ZXMgKHJlbGF0aXZlCmFidW5kYW5jZSB2ZXJzdXMgcmFyZWZpZWQgY291bnRzLCBib3RoIHdpdGggYW5kIHdpdGhvdXQgbG9nIHRyYW5zZm9ybWF0aW9uKS4KCiMjIyAgVGFzayAxMGEKVHJ5IHRvIGNvbWJpbmUgdHdvIGRhdGEgc2V0cyB0byBwZXJmb3JtIGNyb3NzLXN0dWR5IGNvbXBhcmlzb25zCihtZXRhLWFuYWx5c2lzKS4gWW91IGNhbiBlLmcuIHVzZSBjYmluZCBhbmQgcmJpbmQgb24gdG93IG9mIHRlaCBkYXRhIHNldHMKYWJvdmUuIEludHJvZHVjZSBhbiBhZGRpdG9uYWwgY29sdW1uIGluIHRoZSBtZXRhIGRhdGEgZnJhbWUgdGhhdCBrZWVwcyB0cmFjawpvZiB0aGUgb3JpZ2luYWwgc3R1ZHkuCgojIyMgVGFzayAxMGIKVXNlIFBDb0EgdG8gZXhwbG9yZSBob3cgc3Ryb25nIGJhdGNoIGFuZCBzdHVkeSAocHJvdG9jb2wpIGVmZmVjdHMgYXJlCnJlbGF0aXZlIHRvIGVhY2ggb3RoZXIuCgojIyMgVGFzayAxMGMKUGVyZm9ybSB1bml2YXJpYXRlIFdpbGNveG9uIHRlc3RzIG9uIHRoZSBjb21iaW5lZCBkYXRhIHNldC4gVG8gYWNjb3VudCBmb3IKc3R1ZHkgZWZmZWN0cyBhcyBwb3RlbnRpYWwgY29uZm91bmRlcnMsIHRoZSBjb2luIHBhY2thZ2Ugb2ZmZXJzIHBlcm11dGF0aW9uCmJhc2VkIHRlc3RzIHRoYXQgYWxsb3cgZm9yIGJsb2NraW5nIChieSBzdHVkeSkuIFlvdSBjYW4gc3Vic3RpdHV0ZSB0aGVzZSBpbgp0aGUgY29kZSBhYm92ZSBhbmQgY29tcGFyZSB0aGUgcmVzdWx0cyB0byBhIG5haXZlIHRlc3Qgb24gdGhlIGNvbWJpbmVkIGRhdGEuCgoqKkhpbnQ6KiogdGhlIHN5bnRheCBpcyBhIGJpdCBkaWZmZXJlbnQsIHRoZSBiZWxvdyBzaG91bGQgaGVscApgYGB7cn0KI3B2YWx1ZSh3aWxjb3hfdGVzdChtZXRhJEdyb3VwIH4gZmlsdC5yZWwuYWJbdCxdIHwgYXMuZmFjdG9yKG1ldGEkU3R1ZHkpKSkKYGBgCgoKCgojIFNlc3Npb24gaW5mb3JtYXRpb24KCmBgYHtyIGNhY2hlPUZBTFNFfQpzZXNzaW9uSW5mbygpCmBgYAoK